package com.idanchuang.support.etcache;

import com.idanchuang.support.etcache.local.LocalCache;
import com.idanchuang.support.etcache.local.LocalKey;
import com.idanchuang.support.etcache.remote.RemoteCache;
import com.idanchuang.support.etcache.remote.codec.ValueCodec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 核心实现类
 *
 * @author yjy
 * Created at 2021/12/21 9:51 上午
 */
public class EtCache<K, V> extends AbstractCache<K, V> {

    private static final Logger logger = LoggerFactory.getLogger(EtCache.class);
//    private final ExecutorService asyncWorker = new ThreadPoolExecutor(1, 1,
//            1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(10000),
//            (r -> new Thread(r, "et-cache-async")),
//            (r, e) -> {
//                if (!e.isShutdown()) {
//                    r.run();
//                    logger.warn("EtCache asyncWorker queue is full, run by caller");
//                }
//            });

    private final LocalCache<K, V> localCache;
    private final RemoteCache<K, V> remoteCache;
    private final CacheLoader<K, V> cacheLoader;
    private boolean destroyed = false;

    public EtCache(EtCacheManager manager, String cacheName,
                   LocalCache<K, V> localCache,
                   RemoteCache<K, V> remoteCache,
                   CacheLoader<K, V> cacheLoader) {
        super(manager, cacheName);
        this.localCache = localCache;
        this.remoteCache = remoteCache;
        this.cacheLoader = cacheLoader;
    }

    @Override
    public V get(K key) {
        checkState();
        // get from local
        V v = localCache.get(key);
        if (v != null) {
            return v;
        }
        // get from remote
        v = remoteCache.get(key);
        if (v != null) {
            // write local
            localCache.set(key, v);
            return v;
        }
        if (this.cacheLoader == null) {
            return null;
        }
        // get from loader
        v = this.cacheLoader.load(key);
        if (v == null) {
            return null;
        }
        // write local
        localCache.set(key, v);
        // async write remote
        final V finalV = v;
//        asyncWorker.execute(() -> {
            remoteCache.set(key, finalV);
//        });
        return v;
    }

    @Override
    public Map<K, V> getAll(Iterable<K> keys) {
        checkState();
        Map<K, V> map = new HashMap<>();
        for (K k : keys) {
            map.put(k, get(k));
        }
        return map;
    }

    @Override
    public void set(K key, V value) {
        checkState();
        // write local
        localCache.set(key, value);
        // async write remote
//        asyncWorker.execute(() -> {
            remoteCache.set(key, value);
//        });
    }

    @Override
    public void set(K key, V value, long expire, TimeUnit timeUnit) {
        checkState();
        // write local
        localCache.set(key, value);
//        // async write remote
//        asyncWorker.execute(() -> {
            remoteCache.set(key, value, expire, timeUnit);
//        });
    }

    @Override
    public void setAll(Map<K, V> map) {
        checkState();
        // write local
        localCache.setAll(map);
        // async write remote
//        asyncWorker.execute(() -> {
            // write remote
            remoteCache.setAll(map);
//        });
    }

    @Override
    public void setAll(Map<K, V> map, long expire, TimeUnit timeUnit) {
        checkState();
        // write local
        localCache.setAll(map);
//         async write remote
//        asyncWorker.execute(() -> {
            // write remote
            remoteCache.setAll(map, expire, timeUnit);
//        });
    }

    @Override
    public void remove(K key) {
        checkState();
        // remove remote
        remoteCache.remove(key);
        // remove local
        localCache.remove(key);
    }

    @Override
    public void removeAll(Iterable<K> keys) {
        checkState();
        remoteCache.removeAll(keys);
        localCache.removeAll(keys);
    }

    @Override
    public void refreshLocal(LocalKey<K> localKey) {
        checkState();
        localCache.removeAccurate(localKey);
    }

    @Override
    public int estimatedLocalSize() {
        checkState();
        return localCache.estimatedSize();
    }

    @Override
    public ValueCodec getKeyCodec() {
        return remoteCache.getKeyCodec();
    }

    @Override
    public ValueCodec getValueCodec() {
        return remoteCache.getValueCodec();
    }

    /**
     * 检查缓存状态
     */
    private void checkState() {
        if (destroyed) {
            throw new IllegalStateException("The EtCache of [" + this.cacheName + "] is destroyed");
        }
    }

    @Override
    public void destroy() {
        if (!this.destroyed) {
            logger.info("EtCache [{}] destroy.", this.cacheName);
            this.destroyed = true;
            localCache.destroy();
            remoteCache.destroy();
        }
    }

}
